package com.atguigu.gmall.order.biz.impl;

import com.atguigu.gmall.model.ware.WareDeductMsg;
import com.google.common.collect.Lists;

import com.atguigu.gmall.common.consts.RabbitConst;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.JsonUtil;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.PaymentType;
import com.atguigu.gmall.model.enums.PaymentWay;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.mq.to.order.OrderCreateMsg;
import com.atguigu.gmall.model.order.entity.OrderDetail;
import com.atguigu.gmall.model.order.entity.OrderStatusLog;
import com.atguigu.gmall.model.order.entity.PaymentInfo;
import com.atguigu.gmall.model.ware.WareStockMsg;
import com.atguigu.gmall.order.service.OrderStatusLogService;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import java.math.BigDecimal;

import com.atguigu.gmall.common.config.interceptor.component.UserAuthComponent;
import com.atguigu.gmall.common.consts.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.MathUtil;
import com.atguigu.gmall.feign.product.SkuDetailItemFeignClient;
import com.atguigu.gmall.feign.user.UserFeignClient;
import com.atguigu.gmall.feign.ware.WareFeignClient;
import com.atguigu.gmall.model.order.entity.OrderInfo;
import com.atguigu.gmall.model.order.vo.OrderSubmitVo;
import com.atguigu.gmall.model.user.entity.UserAddress;

import com.atguigu.gmall.feign.cart.CartFeignClient;
import com.atguigu.gmall.model.cart.entity.CartInfo;
import com.atguigu.gmall.model.order.vo.OrderConfirmVo;
import com.atguigu.gmall.order.biz.OrderService;
import com.atguigu.gmall.order.service.OrderDetailService;
import com.atguigu.gmall.order.service.OrderInfoService;
import com.atguigu.gmall.order.service.PaymentInfoService;
import com.atguigu.gmall.rabbit.component.RabbitService;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author:lyd
 **/
@Service
public class OrderServiceImpl implements OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
    @Autowired
    CartFeignClient cartFeignClient;
    @Autowired
    SkuDetailItemFeignClient skuDetailItemFeignClient;
    @Autowired
    UserFeignClient userFeignClient;
    @Autowired
    UserAuthComponent userAuthComponent;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    ThreadPoolExecutor executor;
    @Autowired
    WareFeignClient wareFeignClient;
    @Autowired
    OrderInfoService orderInfoService;
    @Autowired
    OrderDetailService orderDetailService;

    @Autowired
    OrderStatusLogService orderStatusLogService;

    @Autowired
    RabbitService rabbitService;
    @Autowired
    PaymentInfoService paymentInfoService;

    @Override
    public OrderConfirmVo getOrderConfirmVo() {
        OrderConfirmVo vo = new OrderConfirmVo();

        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        //1、获取订单详细信息
        CompletableFuture<List<CartInfo>> dataFuture = CompletableFuture.supplyAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            List<CartInfo> data = cartFeignClient.checkedList().getData();
            RequestContextHolder.resetRequestAttributes();
            return data;
        }, executor);
        CompletableFuture<List<OrderConfirmVo.Detail>> detailListFuture = dataFuture.thenApplyAsync(data -> {
            List<OrderConfirmVo.Detail> detailList = data.stream()
                    .map(item -> {
                        OrderConfirmVo.Detail detail = new OrderConfirmVo.Detail();
                        detail.setSkuId(item.getSkuId());
                        detail.setImgUrl(item.getImgUrl());
                        detail.setSkuName(item.getSkuName());
                        //远程调用，获取实时价格
                        BigDecimal skuPrice = skuDetailItemFeignClient.getSkuPrice(item.getSkuId()).getData();
                        detail.setOrderPrice(skuPrice);
                        detail.setSkuNum(item.getSkuNum());
                        //远程调用，获取库存;1：有货，0：无货
                        String hasStock = wareFeignClient.hasStock(item.getSkuId(), item.getSkuNum().intValue());
                        detail.setHasStock(Integer.parseInt(hasStock));
                        return detail;
                    }).collect(Collectors.toList());
            vo.setDetailArrayList(detailList);
            return detailList;
        });

        //2、获取收货地址
        CompletableFuture<Void> userAddressListFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            List<UserAddress> userAddressList = userFeignClient.getAddressList().getData();
            RequestContextHolder.resetRequestAttributes();
            vo.setUserAddressList(userAddressList);
        }, executor);

        //3、计算总金额和总数量
        CompletableFuture<Void> totalAmountFuture = detailListFuture.thenAcceptAsync(detailList -> {
            BigDecimal totalAmount = detailList.stream()
                    .map(item -> item.getOrderPrice().multiply(new BigDecimal(item.getSkuNum())))
                    .reduce(BigDecimal.ZERO, (a, b) -> a.add(b));//  使用identity值作为初始值
            vo.setTotalAmount(totalAmount);
        }, executor);
        CompletableFuture<Void> TotalNumFuture = detailListFuture.thenAcceptAsync(detailList -> {
            Long totalNum = detailList.stream()
                    .map(item -> item.getSkuNum())
                    .reduce(0L, (a, b) -> a + b);
            vo.setTotalNum(totalNum);
        }, executor);

        //4、计算对外交易号 tradeNo,用于防重复提交
        CompletableFuture<Void> tokenFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            String tradeNo = "ATGUIGU" + "_" + System.currentTimeMillis() + "_" + userAuthComponent.getUserAuth().getUserId();
            stringRedisTemplate.opsForValue().set(RedisConst.ORDER_PREVENT_DUPLICATE_SUBMIT + tradeNo, "2", 10, TimeUnit.MINUTES);
            RequestContextHolder.resetRequestAttributes();
            vo.setTradeNo(tradeNo);
        }, executor);
        CompletableFuture.allOf(userAddressListFuture, totalAmountFuture, TotalNumFuture, tokenFuture)
                .join();

        return vo;
    }

    /**
     * 提交订单需要做前置校验
     * 1、校验防重提交
     * 2、校验价格
     * 3、校验库存
     *
     * @param tradeNo  对外交易号
     * @param submitVo 订单提交的数据
     * @return
     */
    @Transactional
    @Override
    public Long submitOrder(String tradeNo, OrderSubmitVo submitVo) {
        //1、校验防重提交
        Boolean delete = stringRedisTemplate.delete(RedisConst.ORDER_PREVENT_DUPLICATE_SUBMIT + tradeNo);
        if (Boolean.FALSE.equals(delete)) {
            throw new GmallException(ResultCodeEnum.ORDER_MULTIPLE_SUBMIT);
        }
        //2、校验价格
        List<String> skuNames = submitVo.getOrderDetailList()
                .stream()
                .parallel()
                .filter(item -> {
                    BigDecimal realPrice = skuDetailItemFeignClient.getSkuPrice(item.getSkuId()).getData();
                    BigDecimal orderPrice = item.getOrderPrice();
                    return !MathUtil.decimalEquals(realPrice, orderPrice);
                })
                .map(item -> item.getSkuName())
                .collect(Collectors.toList());
        if (skuNames != null && skuNames.size() > 0) {
            ResultCodeEnum orderSkuPriceChange = ResultCodeEnum.ORDER_SKU_PRICE_CHANGE;
            String str = JsonUtil.objToJsonStr(skuNames);
            throw new GmallException(orderSkuPriceChange.getMessage() + str, orderSkuPriceChange.getCode());
        }
        //3、校验库存
        List<String> stockSkuNames = submitVo.getOrderDetailList()
                .stream()
                .parallel()
                .filter(item -> {
                    String hasStock = wareFeignClient.hasStock(item.getSkuId(), item.getSkuNum());
                    return !"1".equals(hasStock);
                })
                .map(item -> item.getSkuName())
                .collect(Collectors.toList());
        if (stockSkuNames != null && stockSkuNames.size() > 0) {
            Integer code = ResultCodeEnum.ORDER_SKU_STOCK_CHANGE.getCode();
            String message = ResultCodeEnum.ORDER_SKU_PRICE_CHANGE.getMessage();
            String str = JsonUtil.objToJsonStr(stockSkuNames);
            throw new GmallException(message + str, code);
        }

        //4、保存订单信息
        //4、1 保存订单基本信息 order_info
        OrderInfo orderInfo = getOrderInfo(tradeNo, submitVo);
        orderInfoService.save(orderInfo);
        Long orderId = orderInfo.getId();

        //4、2 保存订单详情 order_detail
        List<OrderDetail> orderDetails = getOrderDetails(submitVo, orderId);
        orderDetailService.saveBatch(orderDetails);

        //5、记录订单状态日志
        OrderStatusLog orderStatusLog = new OrderStatusLog();
        orderStatusLog.setUserId(orderInfo.getUserId());
        orderStatusLog.setOrderId(orderId);
        orderStatusLog.setOrderStatus(OrderStatus.UNPAID.name());
        orderStatusLog.setOperateTime(new Date());
        orderStatusLogService.save(orderStatusLog);

        //6、发送消息给order交换机
        OrderCreateMsg msg = new OrderCreateMsg(orderId, orderInfo.getUserId(),
                new Date(),
                RabbitConst.ROUTING_KEY_ORDER_CREATE);
        rabbitService.sendMsg(RabbitConst.EXCHANGE_ORDER_EVENT,
                RabbitConst.ROUTING_KEY_ORDER_CREATE,
                msg);
        return orderId;
    }

    /**
     * 关闭订单
     *
     * @param msg
     */
    @Transactional
    @Override
    public void closeOrder(OrderCreateMsg msg) {

        /**
         * 对应sql:
         * update order_info set order_status="CLOSED",process_status="CLOSED"
         * where id=? and user_id=? and order_status="UNPAID"
         */
        boolean update = orderInfoService.lambdaUpdate()
                .eq(OrderInfo::getId, msg.getOrderId())
                .eq(OrderInfo::getUserId, msg.getUserId())
                .eq(OrderInfo::getOrderStatus, OrderStatus.UNPAID.name())//利用CAS思想保证幂等性
                .set(OrderInfo::getOrderStatus, OrderStatus.CLOSED.name())
                .set(OrderInfo::getProcessStatus, OrderStatus.CLOSED.name())
                .set(OrderInfo::getOperateTime, new Date())
                .update();
        if (update) {
            //记录订单日志流转
            OrderStatusLog statusLog = new OrderStatusLog();
            statusLog.setUserId(msg.getUserId());
            statusLog.setOrderId(msg.getOrderId());
            statusLog.setOrderStatus(OrderStatus.CLOSED.name());
            statusLog.setOperateTime(new Date());
            orderStatusLogService.save(statusLog);
        } else {
            //代表 订单状态已经不是期望状态
            log.info("订单已被支付，无需关闭； 订单号：{} ，用户id：{}", msg.getOrderId(), msg.getUserId());
        }
    }

    @Transactional
    @Override
    public void orderStatusToPayed(Map<String, String> msg) throws ParseException {
        //1、修改订单状态
        String outTradeNo = msg.get("out_trade_no");
        String[] str = outTradeNo.split("_");
        long userId = Long.parseLong(str[str.length - 1]);
        OrderInfo orderInfo = orderInfoService.lambdaQuery()
                .eq(OrderInfo::getOutTradeNo, outTradeNo)
                .eq(OrderInfo::getUserId, userId)
                .one();
        //当订单状态为未支付或者已支付时，才更新订单状态为已支付
        boolean update = orderInfoService.lambdaUpdate()
                .eq(OrderInfo::getOutTradeNo, outTradeNo)
                .eq(OrderInfo::getUserId, userId)
                .in(OrderInfo::getOrderStatus, OrderStatus.UNPAID.name(), OrderStatus.CLOSED.name())//利用CAS思想保证幂等性
                .set(OrderInfo::getOrderStatus, OrderStatus.PAID.name())
                .set(OrderInfo::getProcessStatus, OrderStatus.PAID.name())
                .set(OrderInfo::getOperateTime, new Date())
                .update();
        if (update) {
            //2、记录订单状态流转日志
            OrderStatusLog statusLog = new OrderStatusLog();
            statusLog.setUserId(userId);
            statusLog.setOrderId(orderInfo.getId());
            statusLog.setOrderStatus(OrderStatus.PAID.name());
            statusLog.setOperateTime(new Date());
            orderStatusLogService.save(statusLog);

            //3、记录订单支付信息
            PaymentInfo paymentInfo = new PaymentInfo();
            paymentInfo.setUserId(orderInfo.getUserId());
            paymentInfo.setOutTradeNo(outTradeNo);
            paymentInfo.setOrderId(orderInfo.getId());
            paymentInfo.setPaymentType(PaymentType.ALIPAY.name());
            //支付宝返回的支付宝交易号
            paymentInfo.setTradeNo(msg.get("trade_no"));
            paymentInfo.setTotalAmount(new BigDecimal(msg.get("total_amount")));
            paymentInfo.setSubject(msg.get("subject"));
            paymentInfo.setPaymentStatus(msg.get("trade_status"));
            paymentInfo.setCreateTime(new Date());
            Date date = getDate(msg);
            paymentInfo.setCallbackTime(date);
            paymentInfo.setCallbackContent(JsonUtil.objToJsonStr(msg));
            paymentInfoService.save(paymentInfo);

            //4、给库存系统发消息，扣减商品库存
            WareStockMsg wareStockMsg = getWareStockMsg(orderInfo, userId);
            rabbitService.sendMsg(RabbitConst.EXCHANGE_DIRECT_WARE_STOCK, RabbitConst.ROUTING_KEY_WARE_STOCK, wareStockMsg);
        }
    }

    @Transactional
    @Override
    public void orderDeductWareStock(Message message, Channel channel) {
        String str = new String(message.getBody());
        WareDeductMsg wareDeductMsg = JsonUtil.strToObj(str, WareDeductMsg.class);
        String status = wareDeductMsg.getStatus();
        OrderStatus orderStatus = OrderStatus.WAITING_DELEVER;
        ProcessStatus processStatus = ProcessStatus.WAITING_DELEVER;
        switch (status) {
            case "DEDUCTED":
                orderStatus = OrderStatus.WAITING_DELEVER;
                processStatus = ProcessStatus.WAITING_DELEVER;
                break;
            case "OUT_OF_STOCK":
                orderStatus = OrderStatus.WAITING_SCHEDULE;
                processStatus = ProcessStatus.STOCK_EXCEPTION;
                break;
        }
        //1、根据库存系统扣减库存返回的status来判断订单此时的状态
        boolean update = orderInfoService.lambdaUpdate()
                .eq(OrderInfo::getId, wareDeductMsg.getOrderId())
                .eq(OrderInfo::getOrderStatus, OrderStatus.PAID.name())
                .set(OrderInfo::getOrderStatus, orderStatus.name())
                .set(OrderInfo::getProcessStatus, processStatus.name())
                .set(OrderInfo::getOperateTime, new Date())
                .update();
        if (update) {
            OrderStatusLog statusLog = new OrderStatusLog();
            //库存系统缺陷，没有传递分片键，只能全库全表扫描
            OrderInfo orderInfo = orderInfoService.getById(wareDeductMsg.getOrderId());
            statusLog.setUserId(orderInfo.getUserId());
            statusLog.setOrderId(orderInfo.getId());
            statusLog.setOrderStatus(orderStatus.name());
            statusLog.setOperateTime(new Date());
            orderStatusLogService.save(statusLog);
        }
        if ("OUT_OF_STOCK".equalsIgnoreCase(status)) {
            log.warn("商品超卖，及时补货！");
        }

    }

    /**
     * 封装库存扣减消息
     *
     * @param orderInfo
     * @param userId
     * @return
     */
    private WareStockMsg getWareStockMsg(OrderInfo orderInfo, long userId) {
        WareStockMsg wareStockMsg = new WareStockMsg();
        wareStockMsg.setOrderId(orderInfo.getId());
        wareStockMsg.setConsignee(orderInfo.getConsignee());
        wareStockMsg.setConsigneeTel(orderInfo.getConsigneeTel());
        wareStockMsg.setOrderComment(orderInfo.getOrderComment());
        wareStockMsg.setOrderBody(orderInfo.getTradeBody());
        wareStockMsg.setDeliveryAddress(orderInfo.getDeliveryAddress());
        //支付方式：  ‘1’ 为货到付款，‘2’为在线支付。
        wareStockMsg.setPaymentWay("2");

        List<WareStockMsg.Detail> details = orderDetailService.lambdaQuery()
                .eq(OrderDetail::getOrderId, orderInfo.getId())
                .eq(OrderDetail::getUserId, userId)
                .list().stream()
                .map(orderDetail -> {
                    WareStockMsg.Detail detail = new WareStockMsg.Detail();
                    detail.setSkuId(orderDetail.getSkuId());
                    detail.setSkuNum(orderDetail.getSkuNum());
                    detail.setSkuName(orderDetail.getSkuName());
                    return detail;
                }).collect(Collectors.toList());
        wareStockMsg.setDetails(details);
        return wareStockMsg;
    }

    private static Date getDate(Map<String, String> msg) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(msg.get("notify_time"));
        return date;
    }

    /**
     * 封装订单详情数据 order_detail
     *
     * @param submitVo
     * @param orderId
     * @return
     */
    private List<OrderDetail> getOrderDetails(OrderSubmitVo submitVo, Long orderId) {
        List<OrderDetail> orderDetails = submitVo.getOrderDetailList()
                .stream()
                .map(item -> {
                    OrderDetail orderDetail = new OrderDetail();
                    orderDetail.setUserId(userAuthComponent.getUserAuth().getUserId());
                    orderDetail.setOrderId(orderId);
                    orderDetail.setSkuId(item.getSkuId());
                    orderDetail.setSkuName(item.getSkuName());
                    orderDetail.setImgUrl(item.getImgUrl());
                    orderDetail.setOrderPrice(item.getOrderPrice());
                    orderDetail.setSkuNum(item.getSkuNum());
                    orderDetail.setCreateTime(new Date());

                    //分割 实际支付金额； 当前这个商品的实际支付总价;
                    // 实际支付金额 = 当前商品原价 - 均摊优惠价；
                    // 用于退款。
                    orderDetail.setSplitTotalAmount(item.getOrderPrice().multiply(new BigDecimal(item.getSkuNum())));

                    // 促销分摊金额
                    orderDetail.setSplitActivityAmount(new BigDecimal(0));
                    // 优惠券分摊金额
                    orderDetail.setSplitCouponAmount(new BigDecimal(0));
                    return orderDetail;
                }).collect(Collectors.toList());
        return orderDetails;
    }

    /**
     * 封装订单基本信息 order_info
     *
     * @param tradeNo
     * @param submitVo
     * @return
     */
    private OrderInfo getOrderInfo(String tradeNo, OrderSubmitVo submitVo) {
        OrderInfo orderInfo = new OrderInfo();
        //收货人
        orderInfo.setConsignee(submitVo.getConsignee());
        //收货人电话
        orderInfo.setConsigneeTel(submitVo.getConsigneeTel());
        //订单总金额
        BigDecimal totalAmount = submitVo.getOrderDetailList()
                .stream()
                .map(item -> item.getOrderPrice().multiply(new BigDecimal(item.getSkuNum())))
                .reduce(BigDecimal.ZERO, (a, b) -> a.add(b));
        orderInfo.setTotalAmount(totalAmount);
        //订单状态
        String unpaid = OrderStatus.UNPAID.name();
        orderInfo.setOrderStatus(unpaid);
        orderInfo.setUserId(userAuthComponent.getUserAuth().getUserId());
        //支付方式
        String paymentWay = PaymentWay.ONLINE.name();
        orderInfo.setPaymentWay(paymentWay);
        //送货地址
        orderInfo.setDeliveryAddress(submitVo.getDeliveryAddress());
        orderInfo.setOrderComment(submitVo.getOrderComment());
        //对外交易号
        orderInfo.setOutTradeNo(tradeNo);
        orderInfo.setTradeBody(submitVo.getOrderDetailList().get(0).getSkuName());
        //创建时间
        orderInfo.setCreateTime(new Date());
        //过期时间，30分钟后过期
        long expireTime = System.currentTimeMillis() + 1000 * 60 * 30;
        orderInfo.setExpireTime(new Date(expireTime));
        //订单进度，给后台管理员看的
        String unpaidStr = ProcessStatus.UNPAID.name();
        orderInfo.setProcessStatus(unpaidStr);

        //出库以后，库存系统对接物流提供物流单号，此时再改数据库的此订单物流单号即可
        orderInfo.setTrackingNo("");

        //拆单的父id
        orderInfo.setParentOrderId(0L);
        orderInfo.setImgUrl(submitVo.getOrderDetailList().get(0).getImgUrl());
        //省id； 没用
        orderInfo.setProvinceId(0L);
        orderInfo.setOperateTime(new Date());

        //优惠券金额，活动金额，未做
        orderInfo.setActivityReduceAmount(new BigDecimal("0"));
        orderInfo.setCouponAmount(new BigDecimal("0"));

        //原价金额： 促销优惠券没有减之前的金额；
        orderInfo.setOriginalTotalAmount(totalAmount);

        //运费
        orderInfo.setFeightFee(new BigDecimal("0"));
        return orderInfo;
    }
}
